package com.fz.us.admin.base.dao.impl;

import com.fz.us.admin.base.bean.search.Search;
import com.fz.us.admin.base.bean.search.SearchEnum;
import com.fz.us.admin.base.bean.search.page.PageList;
import com.fz.us.admin.base.bean.search.page.PagePaging;
import com.fz.us.admin.base.bean.search.page.PageUnique;
import com.fz.us.admin.base.bean.search.projection.ProjectionAggregate;
import com.fz.us.admin.base.bean.search.projection.ProjectionCount;
import com.fz.us.admin.base.bean.search.projection.ProjectionNone;
import com.fz.us.admin.base.bean.search.projection.ProjectionRowCount;
import com.fz.us.admin.base.bean.search.result.*;
import com.fz.us.admin.base.bean.search.rule.*;
import com.fz.us.admin.base.dao.BaseDao;
import com.fz.us.admin.base.entity.BaseEntity;
import com.fz.us.admin.base.utils.CommonUtil;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.*;
import org.hibernate.criterion.*;
import org.hibernate.criterion.Order;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * Dao实现类 - Dao实现类基类
 * ============================================================================
 * 版权所有 2016 。
 *
 * @author fallenpanda
 * @version 1.0 2016-07-13 。
 * ============================================================================
 */
@Repository
public abstract class BaseDaoImpl<T extends BaseEntity, PK extends Serializable> implements BaseDao<T, PK> {

    private Class<T> entityClass;

    @Resource
    protected SessionFactory sessionFactory;

    @SuppressWarnings("unchecked")
    public BaseDaoImpl() {
        this.entityClass = null;
        Class<?> c = getClass();
        Type type = c.getGenericSuperclass();//返回本类的父类,包含泛型参数信息
        if (type instanceof ParameterizedType) {
            Type[] parameterizedType = ((ParameterizedType) type)
                    .getActualTypeArguments();//泛型参数的实际类型
            this.entityClass = (Class<T>) parameterizedType[0];//第一个泛型
        }
    }

    protected Class<T> getEntityClass() {
        return entityClass;
    }

    protected Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    @Override
    public Object getObject(String className, PK id) {
        Assert.notNull(className, "className is required");
        Assert.notNull(id, "id is required");
        return getSession().get(className, id);
    }

    @SuppressWarnings("unchecked")
    @Override
    public T get(PK id) {
        Assert.notNull(id, "id is required");
        return (T) getSession().get(entityClass, id);
    }

    @SuppressWarnings("unchecked")
    @Override
    public T load(PK id) {
        Assert.notNull(id, "id is required");
        return (T) getSession().load(entityClass, id);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> get(PK[] ids) {
        Assert.notEmpty(ids, "ids must not be empty");
        String hql = "from " + entityClass.getName() + " model where model.id in(:ids)";
        return getSession().createQuery(hql).setParameterList("ids", ids).list();
    }

    @SuppressWarnings("unchecked")
    @Override
    public T get(String propertyName, Object value) {
        Assert.hasText(propertyName, "propertyName must not be empty");
        Assert.notNull(value, "value is required");
        String hql = "from " + entityClass.getName() + " model where model." + propertyName + " = ?";
        return (T) getSession().createQuery(hql).setParameter(0, value).uniqueResult();
    }

    @Override
    public boolean isUnique(String propertyName, Object oldValue, Object newValue) {
        Assert.hasText(propertyName, "propertyName must not be empty");
        Assert.notNull(newValue, "newValue is required");
        if (newValue == oldValue || newValue.equals(oldValue)) {
            return true;
        }
        if (newValue instanceof String) {
            if (oldValue != null && StringUtils.equalsIgnoreCase((String) oldValue, (String) newValue)) {
                return true;
            }
        }
        T object = get(propertyName, newValue);
        return (object == null);
    }

    @Override
    public boolean isExist(String propertyName, Object value) {
        Assert.hasText(propertyName, "propertyName must not be empty");
        Assert.notNull(value, "value is required");
        T object = get(propertyName, value);
        return (object != null);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> getList(String propertyName, Object value) {
        Assert.hasText(propertyName, "propertyName must not be empty");
        // Assert.notNull(value, "value is required");
        String hql;
        if (value == null) {
            hql = "from " + entityClass.getName() + " model where model."
                    + propertyName + " is null";
            return getSession().createQuery(hql).list();
        }
        hql = "from " + entityClass.getName() + " model where model."
                + propertyName + " = ?";
        return getSession().createQuery(hql).setParameter(0, value).list();
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> getAll() {
        String hql = "from " + entityClass.getName();
        return getSession().createQuery(hql).list();
    }

    @Override
    public Long getTotalCount() {
        String hql = "select count(*) from " + entityClass.getName();
        return (Long) getSession().createQuery(hql).uniqueResult();
    }

    @SuppressWarnings("unchecked")
    @Override
    public PK save(T entity) {
        Assert.notNull(entity, "entity is required");
        return (PK) getSession().save(entity);
    }

    @Override
    public void update(T entity) {
        Assert.notNull(entity, "entity is required");
        getSession().update(entity);
    }

    @Override
    public void delete(T entity) {
        Assert.notNull(entity, "entity is required");
        getSession().delete(entity);
    }

    @Override
    public void delete(PK id) {
        Assert.notNull(id, "id is required");
        T entity = load(id);
        getSession().delete(entity);
    }

    @Override
    public void delete(PK[] ids) {
        Assert.notEmpty(ids, "ids must not be empty");
        for (PK id : ids) {
            T entity = load(id);
            getSession().delete(entity);
        }
    }

    @Override
    public void flush() {
        getSession().flush();
    }

    @Override
    public void clear() {
        getSession().clear();
    }

    @Override
    public void evict(Object object) {
        Assert.notNull(object, "object is required");
        getSession().evict(object);
    }

    @Override
    public Query createQuery(String hql) {
        return getSession().createQuery(hql);
    }

    @Override
    public SQLQuery createSqlQuery(String sql){
        return getSession().createSQLQuery(sql);
    }

    @Override
    public Object getUniqueResult(String hql) {
        return createQuery(hql).uniqueResult();
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> getList(String hql) {
        return createQuery(hql).list();
    }

    @Override
    public Object getUniqueResult(String hql, Map<String, Object> params) {
        Query query = createQuery(hql);
        setParams(query, params);
        return query.uniqueResult();
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> getList(String hql, Map<String, Object> params) {
        Query query = createQuery(hql);
        setParams(query, params);
        return query.list();
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Object[]> getObjectsList(String hql, Map<String, Object> params) {
        Query query = createQuery(hql);
        setParams(query, params);
        return query.list();
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Map<String, Object>> getMapList(String hql, Map<String, Object> params) {
        Query query = createQuery(hql);
        setParams(query, params);
        return query.list();
    }

    private void setParams(Query query, Map<String, Object> params) {
        if (params != null && params.size() > 0) {
            for (String key : params.keySet()) {
                Object value = params.get(key);
                if (value instanceof Collection) {
                    query.setParameterList(key, (Collection<?>) value);
                } else if (value instanceof Object[]) {
                    query.setParameterList(key, (Object[]) value);
                } else if (value instanceof Date){
                    query.setDate(key, (Date) value);
                } else {
                    query.setParameter(key, value);
                }
            }
        }
    }

    @Override
    public SearchResult findBySearch(Search search) {
        if (search == null) {
            search = Search.simpleSearchList();
        }

        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(entityClass);
        Criteria criteria = detachedCriteria.getExecutableCriteria(getSession());

        // Alias 集合
        List<String> aliasList = new ArrayList<String>();

        // 添加查询条件
        initRules(criteria, aliasList, search.getRules());

        if (search.getPage() instanceof PageUnique) {
            if (search.getPage().getProjection() instanceof ProjectionNone) {
                criteria.setProjection(null);
                criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
                return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
            } else if (search.getPage().getProjection() instanceof ProjectionRowCount) {
                criteria.setProjection(Projections.rowCount());// count(*)
                return SearchResultCount.countResult(search, (long) Integer.parseInt(criteria.uniqueResult().toString()));
            } else if (search.getPage().getProjection() instanceof ProjectionCount) {
                if (SearchEnum.ProjectionType.count.equals(search.getPage().getProjection().getProjectionType())) {
                    criteria.setProjection(Projections.count(((ProjectionCount) search.getPage().getProjection()).getField()));// count(field)
                    return SearchResultCount.countResult(search, Long.parseLong(criteria.uniqueResult().toString()));
                } else if (SearchEnum.ProjectionType.countDistinct.equals(search.getPage().getProjection().getProjectionType())) {
                    criteria.setProjection(Projections.countDistinct(((ProjectionCount) search.getPage().getProjection()).getField()));// countDistinct(field)
                    return SearchResultCount.countResult(search, Long.parseLong(criteria.uniqueResult().toString()));
                }
            } else if (search.getPage().getProjection() instanceof ProjectionAggregate) {
                String field = ((ProjectionAggregate) search.getPage().getProjection()).getField();

                if (SearchEnum.ProjectionType.max.equals(search.getPage().getProjection().getProjectionType())) {
                    criteria.setProjection(Projections.max(field));// max(field)
                    return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
                } else if (SearchEnum.ProjectionType.min.equals(search.getPage().getProjection().getProjectionType())) {
                    criteria.setProjection(Projections.min(field));// min(field)
                    return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
                } else if (SearchEnum.ProjectionType.avg.equals(search.getPage().getProjection().getProjectionType())) {
                    criteria.setProjection(Projections.avg(field));// avg(field)
                    return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
                } else if (SearchEnum.ProjectionType.sum.equals(search.getPage().getProjection().getProjectionType())) {
                    criteria.setProjection(Projections.sum(field));// sum(field)
                    return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
                }
            }
        } else if (search.getPage() instanceof PageList) {
            if (search.getPage() instanceof PagePaging) {
                // 计算总记录数
                criteria.setProjection(Projections.rowCount());
                long totalCount = Long.parseLong(criteria.uniqueResult().toString());

                criteria.setProjection(null);// 重置查询目标
                criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);

                // 添加排序
                initOrder(criteria, aliasList, search.getOrders());

                // 添加分页
                initPager(criteria, (PagePaging) search.getPage());

                List results = criteria.list();
                if (results == null) {
                    results = new ArrayList();
                }

                long pageCount = 0;
                Integer pageSize = ((PagePaging) search.getPage()).getPageSize();
                if(pageSize == 0){
                    return SearchResultPaging.list(search, ((PagePaging) search.getPage()).getPageNumber(), pageSize, totalCount, pageCount, results);
                }
                if (totalCount % pageSize == 0) {
                    pageCount = totalCount / pageSize;
                } else {
                    pageCount = (totalCount / pageSize) + 1;
                }

                return SearchResultPaging.list(search, ((PagePaging) search.getPage()).getPageNumber(), pageSize, totalCount, pageCount, results);
            } else {
                // 添加排序
                initOrder(criteria, aliasList, search.getOrders());

                List results = criteria.list();
                if (results == null) {
                    results = new ArrayList();
                }

                return SearchResultList.list(search, results);
            }
        }
        return null;
    }

    private void initRules (Criteria criteria, List<String> aliasList, List<Rule> rules) {
        if (rules != null && rules.size()>0) {
            for (Rule rule : rules) {
                Criterion criterion = getCriterion(criteria, aliasList, rule);
                if (criterion != null)  criteria.add(criterion);
            }
        }
    }

    private Criterion getCriterion (Criteria criteria, List<String> aliasList, Rule rule) {
        if (rule instanceof RuleRelation) {
            Rule[] rules = ((RuleRelation) rule).getRules();

            // 获取多条件
            Criterion[] criterions = new Criterion[rules.length];
            for (int i=0;i<rules.length;i++) {
                criterions[i] = getCriterion(criteria, aliasList, rules[i]);
            }

            // 返回 Criterion 对象
            if (SearchEnum.RuleType.and.equals(rule.getRuleType())) {
                return Restrictions.and(criterions);
            } else if (SearchEnum.RuleType.or.equals(rule.getRuleType())) {
                return Restrictions.or(criterions);
            }
        } else if (rule instanceof RuleNone) {
            String field = ((RuleNone) rule).getField();

            // 获取 propertyName
            String propertyName = initPropertyName(criteria, aliasList, field);

            // 返回 Criterion 对象
            if (SearchEnum.RuleType.isEmpty.equals(rule.getRuleType())) {
                return Restrictions.isEmpty(propertyName);
            } else if (SearchEnum.RuleType.isNotEmpty.equals(rule.getRuleType())) {
                return Restrictions.isNotEmpty(propertyName);
            } else if (SearchEnum.RuleType.isNull.equals(rule.getRuleType())) {
                return Restrictions.isNull(propertyName);
            } else if (SearchEnum.RuleType.isNotNull.equals(rule.getRuleType())) {
                return Restrictions.isNotNull(propertyName);
            }
        } else if (rule instanceof RuleSimple) {
            String field = ((RuleSimple) rule).getField();
            Object value = ((RuleSimple) rule).getData();

            // 获取 propertyName
            String propertyName = initPropertyName(criteria, aliasList, field);

            // 返回 Criterion 对象
            if (SearchEnum.RuleType.eq.equals(rule.getRuleType())) {
                return Restrictions.eq(propertyName, value);
            } else if (SearchEnum.RuleType.ne.equals(rule.getRuleType())) {
                return Restrictions.ne(propertyName, value);
            } else if (SearchEnum.RuleType.gt.equals(rule.getRuleType())) {
                return Restrictions.gt(propertyName, value);
            } else if (SearchEnum.RuleType.ge.equals(rule.getRuleType())) {
                return Restrictions.ge(propertyName, value);
            } else if (SearchEnum.RuleType.lt.equals(rule.getRuleType())) {
                return Restrictions.lt(propertyName, value);
            } else if (SearchEnum.RuleType.le.equals(rule.getRuleType())) {
                return Restrictions.le(propertyName, value);
            } else if (SearchEnum.RuleType.in.equals(rule.getRuleType())) {
                if (value instanceof Object[]) {
                    return Restrictions.in(propertyName, (Object[]) value);
                } else if (value instanceof Collection) {
                    return Restrictions.in(propertyName, (Collection) value);
                }
            }

        } else if (rule instanceof RuleLike) {
            String field = ((RuleLike) rule).getField();
            Object value = ((RuleLike) rule).getData();
            MatchMode matchMode = ((RuleLike) rule).getMatchMode();

            // 获取 propertyName
            String propertyName = initPropertyName(criteria, aliasList, field);

            // 添加查询条件
            if (SearchEnum.RuleType.like.equals(rule.getRuleType())) {
                if (matchMode != null) {
                    return Restrictions.like(propertyName, (String) value, matchMode);
                } else {
                    return Restrictions.like(propertyName, value);
                }
            } else if (SearchEnum.RuleType.ilike.equals(rule.getRuleType())) {
                if (matchMode != null) {
                    return Restrictions.ilike(propertyName, (String) value, matchMode);
                } else {
                    return Restrictions.ilike(propertyName, value);
                }
            }
        } else if (rule instanceof RuleSql) {
            String sql = ((RuleSql) rule).getSql();
            Object[] values = ((RuleSql) rule).getValues();
            org.hibernate.type.Type[] types = ((RuleSql) rule).getTypes();

            // 添加查询条件
            if (SearchEnum.RuleType.sql.equals(rule.getRuleType())) {
                if (values != null && types != null) {
                    return Restrictions.sqlRestriction(sql, values, types);
                } else {
                    return Restrictions.sqlRestriction(sql);
                }
            }
        }
        return null;
    }

    private void initPager (Criteria criteria, PagePaging page) {
        if (page.getPageNumber() > 0) {
            criteria.setFirstResult((page.getPageNumber() - 1) * page.getPageSize());
            criteria.setMaxResults(page.getPageSize());
        }
    }

    private void initOrder (Criteria criteria, List<String> aliasList, List<com.fz.us.admin.base.bean.search.order.Order> orders) {
        if (orders != null && orders.size()>0) {
            for (com.fz.us.admin.base.bean.search.order.Order order : orders) {
                String orderBy = order.getOrderBy();
                SearchEnum.OrderType orderType = order.getOrderType();

                if (StringUtils.isNotEmpty(orderBy) && orderType != null) {
                    // 获取 propertyName
                    String propertyName = initPropertyName(criteria, aliasList, orderBy);
                    if (SearchEnum.OrderType.asc.equals(orderType)) {
                        criteria.addOrder(Order.asc(propertyName));
                    } else {
                        criteria.addOrder(Order.desc(propertyName));
                    }
                }
            }
        }
    }

    private String initPropertyName (Criteria criteria, List<String> aliasList, String field) {
        String propertyName;
        if (StringUtils.contains(field, "_")) {
            String[] fields = StringUtils.split(field, "_");
            for (int j = 0; j < fields.length - 1; j++) {
                String alias = CommonUtil.getAlias(fields, j);
                if (!aliasList.contains(alias)) {
                    criteria.createAlias(CommonUtil.getPath(fields, j), alias);
                    aliasList.add(alias);
                }
            }
            propertyName = CommonUtil.getPath(fields, fields.length - 1);
        } else if (StringUtils.contains(field, ".")) {
            String[] fields = StringUtils.split(field, ".");
            for (int j = 0; j < fields.length - 1; j++) {
                String alias = CommonUtil.getAlias(fields, j);
                if (!aliasList.contains(alias)) {
                    criteria.createAlias(CommonUtil.getPath(fields, j), alias);
                    aliasList.add(alias);
                }
            }
            propertyName = CommonUtil.getPath(fields, fields.length - 1);
        } else {
            propertyName = field;
        }
        return propertyName;
    }

    @Override
    public Object getObjectEntity(String keyId, String className) {
        String hql = "from "+className+" where id=:keyId ";
        List<Object> objectList = getSession().createQuery(hql).setParameter("keyId",keyId).list();
        if(objectList.size()>0){
            return objectList.get(0);
        }
        return null;
    }

}